// Copyright 2009 Pigasus.
// GNU GPL v2.
// Author: John Zhang and Jordan Rupprecht.

#include "loadcsv.h"

#include <iostream>
#include <fstream>

#include "pigasus-types.h"

using namespace std;

typedef unsigned int uint; // Why is this necessary?

bool LoadCSVIntoVector(string filename, vector< vector<string> > *csv_elements) {
  ifstream csv_file(filename.c_str());

  if (!csv_file.is_open()) {
    return false;
  }

  string line;

  while (!csv_file.eof()) {
    getline(csv_file, line);

    if (line.empty()) {
      continue;
    }

    vector<string> elements;
    string buffer;
    bool in_quotes = false;

    do {
      if (in_quotes) {
        // Handles newlines in CSV elements.
        buffer += '\n';
        if (!csv_file.eof()) {
          getline(csv_file, line);
        } else {
          break;
        }
      }

      for (uint i = 0; i < line.size(); i++) {
        char c = line[i];
        if (c == kQuoteChar) {
          // Quote may end a quoted block, or escape another quote.
          if (in_quotes
              && (i+1 < line.size())
              && (line[i+1] == kQuoteChar)) {
            // Two quotes in a row means one scaped quote.
            buffer += line[i+1];
            i++;
          } else {
            in_quotes = !in_quotes;
            // Embedded quote in the middle: a,b"c"d,e
            if (i > 2
                && (line[i-1] != kSeparator)
                && (i+1 < line.size())
                && (line[i+1] != kSeparator)) {
              buffer += c;
            } 
          }
        } else if (c == kSeparator && !in_quotes) {
          elements.push_back(buffer);
          buffer.clear();
        } else {
          buffer += c;
        }
      }
    } while (in_quotes);

    elements.push_back(buffer);
    csv_elements->push_back(elements);
  }

  csv_file.close();
  return true;
}

List LoadCSV(string filename) {
  vector< vector<string> > csv_elements;
  List csv;

  if (!LoadCSVIntoVector(filename, &csv_elements) || csv_elements.empty()) {
    return csv;  // Returns empty list.
  }
  
  // First row of CSV are the headers. Each element will be subsequently mapped
  // to its corresponding header.
  for (uint i = 1; i < csv_elements.size(); i ++) {
    Map *csv_row = new Map();
   
    for (uint j = 0;
         (j < csv_elements[i].size()) && (j < csv_elements[0].size());
         j++) {
      csv_row->Set(csv_elements[0][j], csv_elements[i][j]);
    }
    csv.Append(*csv_row);
    delete csv_row;
  }

  return csv;   
}
